#include <stdio.h>

/**
 * 运算符和表达式
 */
int main() {
    //1、运算符：是一种告诉编译器执行特定的数学或逻辑操作的符号。C 语言内置了丰富的运算符，并提供了以下类型的运算符：
    /*
        算术运算符 用于处理四则运算
        赋值运算符 用于将表达式的值赋给变量
        关系运算符 用于表达式的比较，并返回一个真值或假值
        逻辑运算符 用于根据表达式的值返回真值或假值
        位运算符 用于处理数据的位运算
        逗号运算符 把多个表达式用逗号联接起来（或者说，把这些表达式用逗号分开），构成一个更大的表达式
        条件运算符 第一个表达式作真/假检测，然后根据结果返回两外两个表达式中的一个
        sizeof运算符 用于求字节数长度
        指针运算符(&，*) &取地址运算符 *间接运算符
        点运算符（.） 点运算符 .的左边操作数是一个结果为 结构体的表达式
        箭头运算符（->) 箭头运算符 -> 的左边的操作数是一个 指向结构体的指针
        [] 下标运算符 通过下标访问数组中的元素
     */
    //2、表达式就是通过 运算符 将 运算对象（操作数） 连接起来的式子，就叫表达式。比如a+b, x=a+2, x=y>z, x++
    //3、运算符也叫做操作符，根据运算符操作的运算对象的个数可以将运算符分为以下几种：
    /*
        单目运算符：只需要一个操作数
        双目运算符：需要两个操作数
        三目运算符：需要三个操作数
     */
    // 4、运算符的优先级: ! > 算术运算符 > 关系运算符 > && > || > 条件运算符 > 赋值运算符 > 逗号运算符
    // 5、运算符的结合性: 运算符在执行表达式时运算符和左右操作数结合的顺序，分为：
    /*
        单目运算符：根据操作数在左还是右分为左结合和右结合
        双/三目运算符：从左至右或者从右至左
     */
    int intValue, otherIntValue;

    printf("3.1算术运算符效果一览:\n");
    printf("+运算符(加)。结合性——从左至右。运算结果: %d\n", 10 + 5);
    printf("-运算符(减)。结合性——从左至右。运算结果: %d\n", 10 - 5);
    printf("*运算符(乘)。结合性——从左至右。运算结果: %d\n", 10 * 5);
    printf("/运算符(除)。结合性——从左至右。运算结果: %d\n", 10 / 5);
    printf("%%运算符(取模(取余))。结合性——从左至右。注意两操作数必须为整数。运算结果: %d\n", 10 % 3);
    printf("运算符结合性演示。运算结果: %f\n", 1.0 * 5 / 2);//从左至右，所以是先算出5.0再除2，所以是2.5
    printf("运算符结合性演示。运算结果: %f\n", 5 / 2 * 1.0);//从左至右，所以是先整除算出2再乘1.0，所以是2.0
    intValue = 2;
    otherIntValue = ++intValue;
    //TIPS: 前自增/前自减先计算后引用，后自增/后自减先引用后计算
    printf("++运算符(前自增)。结合性——右结合。运算结果: %d %d \n", intValue, otherIntValue);
    otherIntValue = intValue++;
    printf("++运算符(后自增)。结合性——右结合。运算结果: %d %d \n", intValue, otherIntValue);
    otherIntValue = --intValue;
    printf("--运算符(前自减)。结合性——右结合。运算结果: %d %d \n", intValue, otherIntValue);
    otherIntValue = intValue--;
    printf("--运算符(后自减)。结合性——右结合。运算结果: %d %d \n", intValue, otherIntValue);

    printf("3.2赋值运算符效果一览:\n");
    //专业术语：数据对象、左值、右值
    //数据对象: 赋值表达式语句的目的是把值存储到内存上。而用于存储值的数据存储区域统称为"数据对象"。
    //左值(lvalue): 指用于标识一个特定的数据对象的名字或表达式。特性:1、可修改性，又称为可修改左值 2、是数据对象的标识符，应具有标识符的特性 3、左值不能为常量
    //右值(rvalue): 指的是能赋给可修改的左值的量。特性: 可以是变量可以是常量，也可以是任何可以产生一个值的表达式
    intValue = otherIntValue;
    printf("=运算符(赋值)。结果: %d\n", intValue);
    intValue += 3;
    printf("+=运算符(加等于)。结果: %d\n", intValue);
    intValue -= 4;
    printf("-=运算符(减等于)。结果: %d\n", intValue);
    intValue *= 5;
    printf("*=运算符(乘等于)。结果: %d\n", intValue);
    intValue /= 2;
    printf("/=运算符(除等于)。结果: %d\n", intValue);
    intValue %= 2;
    printf("%%=运算符(模等于)。结果: %d\n", intValue);

    printf("3.3关系运算符效果一览:\n");
    //关系运算符用于比较两个数值中间的关系，所以也叫做"比较运算符"。当比较结果正确时返回1(代表逻辑真-关系成立)，否则返回0(代表逻辑假-关系不成立)。
    //TIPS: 1是代表"真"，但是"真"不是1，而是非0。具体返回什么数字由不同程序决定。
    printf("==运算符(相等于)。结果: %d\n", 4 == 3);
    printf("!=运算符(不等于)。结果: %d\n", 4 != 3);
    printf("<运算符(小于)。结果: %d\n", 4 < 3);
    printf(">运算符(大于)。结果: %d\n", 4 > 3);
    printf("<=运算符(小于等于)。结果: %d\n", 4 <= 4);
    printf(">=运算符(大于等于)。结果: %d\n", 4 >= 1);

    printf("3.4逻辑运算符效果一览:\n");
    //该表达式输出了1(且无论你将中间数换什么数字也依旧如此)，为什么？因为C语言在执行这个表达式的时候分成了两步：
    // 1）第一步：先计算 60 <= x 表达式的结果，很显然不论x的值为多少答案都只有两个：0或者1
    // 2）第二步：用0或者1和70比较大小，很显然结果为1！
    printf("演示表达式的执行规律。结果: %d\n", 60 <= 65 <= 70);
    //所以如果需要结合合多个逻辑组成表达式，应该使用C语言内置的逻辑运算符
    intValue = 65;
    //如果a和b都为真，则结果为真，否则为假。
    printf("&&运算符(与)。结果: %d\n", 60 <= intValue && intValue <= 70);
    //如果a和b有一个为真，则结果为真，二者都为假时，结果为假。
    printf("||运算符(或)。结果: %d\n", 60 <= intValue && intValue <= 64);
    //如果a为假，则!a为真；如果a为真，则!a为假。
    printf("!运算符(非)。结果: %d\n", !(60 <= intValue && intValue <= 64));

    printf("3.5位运算符效果一览:\n");
    //按位与操作，按⼆进制位进⾏"与"运算。运算规则： 0&0=0; 0&1=0; 1&0=0; 1&1=1。有0就是0。
    //60: 0011 1100  30: 0001 1110
    printf("&运算符(按位与)。结果: %d\n", 60 & 30);//28，即为 0001 1100
    //按位或运算符，按⼆进制位进⾏"或"运算。运算规则： 0|0=0; 0|1=1; 1|0=1; 1|1=1。有1就是1。
    printf("|运算符(按位或)。结果: %d\n", 60 | 30);//62，即为 0011 1110
    //取反运算符，按⼆进制位进⾏"取反"运算。运算规则： ~1=0; ~0=1
    printf("~运算符(取反)。结果: %d\n", ~60);//-61，即为 1100 0011
    //异或运算符，按⼆进制位进⾏"异或"运算。运算规则： 0^0=0; 0^1=1; 1^0=1; 1^1=0。相同是0，不同是1。
    printf("^运算符(按位或)。结果: %d\n", 60 ^ 30);//34，即为 0010 0010
    //⼆进制左移运算符。将⼀个运算对象的各⼆进制位全部左移若⼲位（左边的⼆进制位丢弃，右边补0）
    printf("<<运算符(左移)。结果: %d\n", 60 << 2);//240，即为 1111 0000
    //⼆进制右移运算符。将⼀个数的各⼆进制位全部右移若⼲位，正数左补0，负数左补1，右边丢弃
    //TIPS:左移和右移由于基于二进制的特性所以每移一位刚好是2的倍数(只要不发生越界)。
    printf(">>运算符(右移)。结果: %d\n", 60 >> 2);//15，即为 0000 1111

    //扩展:不使用中间变量交换两个整型变量的值
    int a = 5, b = 6;
    a = a + b;
    b = a - b;
    a = a - b;
    //TIPS:要保证a+b不超过数据类型最大值，所以会有bug的可能性
    printf("结果: a = %d,b = %d\n", a, b);
    a = a ^ b;
    b = a ^ b;
    a = a ^ b;
    printf("结果: a = %d,b = %d\n", a, b);
    //扩展:判断整数的奇偶性--输出1是奇，输出0是偶
    printf("判断奇偶性: %d\n", 13 & 1);
    printf("判断奇偶性: %d\n", 14 & 1);
    //扩展:位操作进行高低位交换(高8位与低8位替换)
    printf("交换前的值: %d\n", intValue);
    intValue = (intValue >> 8) | (intValue << 8);
    printf("交换后的值: %d\n", intValue);
    //扩展:寄存器的置位。嵌入式系统总是要用户对变量或寄存器(8位)进行位操作。
    // 将变量的第3个bit位置1（bit位最右边的位第0位），其他位的值不变。
    intValue = 0B00000111;
    printf("置位前的值: %d\n", intValue);
    intValue = intValue | 0x08;
    printf("置位后的值: %d\n", intValue);
    intValue = intValue & (~0x08);
    printf("置位后的值: %d\n", intValue);
    //扩展:数据的"拆解"——在数据的传输过程中需要对整型数据进行拆解，例如 int x = 0x12345678， 我们需要将x分别拆解成0x12 0x34 0x56 0x78进行传输
    intValue = 0x12345678;
    printf("拆解前的值: %d\n", intValue);
    printf("拆解后的值: %d\n", intValue & 0xFF);//0x78
    printf("拆解后的值: %d\n", (intValue >> 8) & 0xFF);//0x56
    printf("拆解后的值: %d\n", (intValue >> 16) & 0xFF);//0x34
    printf("拆解后的值: %d\n", (intValue >> 24) & 0xFF);//0x12
    //扩展:数据的"组装"——将拆解的数据重新组装起来还原成本来的值
    intValue = (intValue & 0xFF) | ((intValue >> 8) & 0xFF) << 8 | ((intValue >> 16) & 0xFF) << 16 |
               ((intValue >> 24) & 0xFF) << 24;
    printf("组装后的值: %d\n", intValue);

    printf("3.5逗号运算符效果展示:\n");
    //语法: 将两个表达式连接起来——表达式1, 表达式2
    //结合性: 是"从左至右"。先计算表达式1，后计算表达式2。
    intValue = 3;
    otherIntValue = intValue *= 2, intValue += 4;//整个表达式最终的值是表达式1的值
    //本质上跟你写 otherIntValue = intValue *= 2; intValue += 4; 没有区别。觉得不直观可以一行行写。
    printf("逗号运算符。结果: %d %d\n", intValue, otherIntValue);

    printf("3.6条件运算符效果展示:\n");
    //语法: 表达式1 ? 表达式2 : 表达式3
    //执行逻辑: 如果表达式1的结果为真，则整个表达式的结果为 表达式2的结果，否则为表达式3的结果
    printf("条件运算符。结果: %d\n", intValue > otherIntValue ? 3 : 4);
    printf("条件运算符。结果: %d\n", intValue < otherIntValue ? 3 : 4);


    return 0;
}